﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace KuiHuaBaoDian.Services.Lockstep {

    /// <summary>
    /// 帧同步命令中转模拟器
    /// </summary>
    public interface ILockstepCommandRelaySimulator : IDisposable {

        public delegate void OnFrameDelegate(LockstepFrame[] frames);

        public delegate void OnNotifyDelegate(ILockstepCommand command);

        event OnFrameDelegate OnFrame;

        event OnNotifyDelegate OnNotify;

        void Send(ILockstepCommand command);

        void Initialize(float frameLength, uint clientCount);
    }

    [Implement(typeof(ILockstepCommandRelaySimulator))]
    internal sealed class KHBD_LockstepCommandRelaySimulator : ILockstepCommandRelaySimulator {

        public event ILockstepCommandRelaySimulator.OnFrameDelegate OnFrame;
        public event ILockstepCommandRelaySimulator.OnNotifyDelegate OnNotify;

        private bool m_IsDisposed;
        void IDisposable.Dispose() {
            m_IsDisposed = true;
            OnFrame = null;
            OnNotify = null;
            m_CommandList = null;
            m_ClientIDSet = null;
            m_IsStarted = false;
            m_IsInitialized = false;
            m_IsPausing = true;
            m_InternalCommandDictionary = null;
            m_FrameQueue = null;
        }

        private List<ILockstepCommand> m_CommandList;
        private HashSet<LockstepClientID> m_ClientIDSet;
        private Dictionary<Type, ILockstepCommand> m_InternalCommandDictionary;
        private List<LockstepFrame> m_FrameQueue;

        private uint m_ClientCount;
        private bool m_IsStarted;
        private bool m_IsInitialized;
        private bool m_IsPausing;

        public void Send(ILockstepCommand command) {
            if (m_IsInitialized) {
                if (!m_IsStarted) {
                    if (command is ILockstepCommand.IClientJoin joinCommand) {
                        if (!m_ClientIDSet.Contains(joinCommand.ClientID)) {
                            m_ClientIDSet.Add(joinCommand.ClientID);
                            OnNotify?.Invoke(joinCommand);
                        }
                        return;
                    } else if (command is ILockstepCommand.IClientPrepared preparedCommand) {
                        if (m_ClientIDSet.Count == m_ClientCount) {
                            var cmd = (ILockstepCommand.IClientPrepared)Activator.CreateInstance(preparedCommand.GetType());
                            cmd.ClientIDList = m_ClientIDSet.ToList();
                            cmd.RandomSeed = (ulong)DateTime.Now.Ticks;
                            m_CommandList.Add(cmd);
                            Daemon().Forget();
                            return;
                        }
                    } else if (command is ILockstepCommand.IClientInitialize) {
                        OnNotify?.Invoke(command);
                        return;
                    }
                } else if (m_IsStarted) {
                    if (command is ILockstepCommand.ISetTimeScale) {
                        m_InternalCommandDictionary[command.GetType()] = command;
                        return;
                    } else if (command is ILockstepCommand.IPause or ILockstepCommand.IResume) {
                        var type = command.GetType();
                        if (!m_InternalCommandDictionary.ContainsKey(type)) {
                            m_InternalCommandDictionary[type] = command;
                        }
                        return;
                    } else if (command is ILockstepCommand.IEnd) {
                        var frames = new LockstepFrame[] { new() { Commands = new ILockstepCommand[] { command }, Index = m_FrameCount } };
                        OnFrame?.Invoke(frames);
                        return;
                    }
                }
                //if (!m_IsPausing) {
                m_CommandList.Add(command);
                //}
            }
        }

        public void Initialize(float frameLength, uint clientCount) {
            m_FrameLength = frameLength;
            m_IsInitialized = true;
            m_ClientIDSet = new();
            m_ClientCount = clientCount;
            m_CommandList = new();
        }

        // 帧用时
        private float m_FrameLength;

        // 总计帧数
        private int m_FrameCount;

        // 时长缩放
        private float m_TimeScale;

        // 累积运行的时长
        private float m_AccumulatedTime;

        // 下一帧的时间
        private float m_NextTime;

        private async UniTaskVoid Daemon() {
            m_IsStarted = true;
            m_FrameCount = 0;
            m_AccumulatedTime = 0f;
            m_NextTime = m_FrameLength;
            m_IsPausing = false;
            m_InternalCommandDictionary = new();
            m_TimeScale = 1f;
            m_FrameQueue = new();
            await KHBD.Context.WaitUntil(() => {
                if (!m_IsDisposed) {
                    if (!m_IsPausing) {
                        m_AccumulatedTime += Time.deltaTime * m_TimeScale;
                        while (m_AccumulatedTime >= m_NextTime && KHBD.Context.IsApplicationPlaying) {
                            m_NextTime += m_FrameLength;
                            Frame();
                        }
                    }
                    InternalFrame();
                }
                return m_IsDisposed;
            });
        }

        private void Frame() {
            if (m_CommandList.Count == 0) {
                var frame = new LockstepFrame {
                    Index = m_FrameCount++,
                };
                Enqueue(frame);
            } else {
                var commands = m_CommandList.ToArray();
                m_CommandList.Clear();
                var frame = new LockstepFrame {
                    Index = m_FrameCount++,
                    Commands = commands
                };
                Enqueue(frame);
            }
            //Debug.Log($"[{nameof(KHBD_LockstepCommandRelaySimulator)}.{nameof(Frame)}()({nameof(m_FrameCount)}={m_FrameCount})]");
        }

        private void InternalFrame() {
            if (m_FrameQueue.Count > 0) {
                var keys = m_InternalCommandDictionary.Keys.ToArray();
                foreach (var key in keys) {
                    var command = m_InternalCommandDictionary[key];
                    if (command is ILockstepCommand.ISetTimeScale setTimeScaleCommand) {
                        m_TimeScale = setTimeScaleCommand.Value;
                    } else if (command is ILockstepCommand.IPause) {
                        m_IsPausing = true;
                    } else {
                        continue;
                    }
                    m_InternalCommandDictionary.Remove(key);
                    var lastFrame = m_FrameQueue[^1];
                    if (!(lastFrame.Commands?.Length > 0)) {
                        lastFrame.Commands = new ILockstepCommand[] {
                            command,
                        };
                    } else {
                        var commands = new ILockstepCommand[lastFrame.Commands.Length + 1];
                        for (var i = 0; i < lastFrame.Commands.Length; i++) {
                            commands[i] = lastFrame.Commands[i];
                        }
                        commands[^1] = command;
                        lastFrame.Commands = commands;
                    }
                }
                Dispatch();
            }
            if (true) {
                var keys = m_InternalCommandDictionary.Keys.ToArray();
                foreach (var key in keys) {
                    var command = m_InternalCommandDictionary[key];
                    if (command is ILockstepCommand.IResume) {
                        m_InternalCommandDictionary.Remove(key);
                        m_IsPausing = false;
                        if (m_CommandList.Count > 0) {
                            if (m_CommandList[0] is not ILockstepCommand.IResume) {
                                m_CommandList.Insert(0, command);
                            }
                        } else {
                            m_CommandList.Add(command);
                        }
                    }
                }
            }
        }


        private void Enqueue(LockstepFrame frame) {
            m_FrameQueue.Add(frame);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Dispatch() {
            var frames = m_FrameQueue.ToArray();
            m_FrameQueue.Clear();
            OnFrame?.Invoke(frames);
        }
    }
}
